<meta charset="utf-8">
(#) Using Dollar Escapes

!!! ERROR: Using Dollar Escapes
   This is an error.

Id
:   `LintImplDollarEscapes`
Summary
:   Using Dollar Escapes
Severity
:   Error
Category
:   Lint Implementation Issues
Platform
:   JDK
Vendor
:   Android Open Source Project
Feedback
:   https://issuetracker.google.com/issues/new?component=192708
Since
:   4.1.0 (October 2020)
Affects
:   Kotlin and Java files and test sources
Editing
:   This check runs on the fly in the IDE editor
Implementation
:   [Source Code](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LintDetectorDetector.kt)
Tests
:   [Source Code](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LintDetectorDetectorTest.kt)

Instead of putting `${"$"}` in your Kotlin raw string literals you can
simply use ＄. This looks like the dollar sign but is instead the full
width dollar sign, U+FF04. And this character does not need to be
escaped in Kotlin raw strings, since it does not start a string
template.

Lint will automatically convert references to ＄ in unit test files into
a real dollar sign, and when pulling results and error messages out of
lint, the dollar sign back into the full width dollar sign.

That means you can use ＄ everywhere instead of `${"$"}`, which makes the
test strings more readable -- especially `$`-heavy code such as
references to inner classes.

!!! Tip
   This lint check has an associated quickfix available in the IDE.

(##) Example

Here is an example of lint warnings produced by this check:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~text
src/test/pkg/MyKotlinLintDetectorTest.kt:22:Error: In unit tests, use
the fullwidth dollar sign, $, instead of $, to avoid having to use
cumbersome escapes. Lint will treat a $ as a $. [LintImplDollarEscapes]
    println("Value=${"$"}")
                   ------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Here are the relevant source files:

`src/test/pkg/MyJavaLintDetector.java`:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~java linenumbers
/* Copyright (C) 2020 The Android Open Source Project */
package test.pkg;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiCallExpression;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.util.PsiTreeUtil;
import com.android.tools.lint.detector.api.Detector;
import org.jetbrains.uast.UFile;
import org.jetbrains.uast.UMethod;
import org.jetbrains.uast.UField;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import org.jetbrains.uast.UCallExpression;
import java.util.EnumSet;

@SuppressWarnings({"MethodMayBeStatic", "ClassNameDiffersFromFileName", "StatementWithEmptyBody", "deprecation"})
public class MyJavaLintDetector extends Detector {
    public static final Issue ISSUE =
            Issue.create(
                    "com.android.namespaced.lint.check.FooDetector",
                    "Wrong use of <LinearLayout>",
                    "As described in "
                        + "https://code.google.com/p/android/issues/detail?id=65351 blah blah blah.",
                    Category.A11Y,
                    3,
                    Severity.WARNING,
                    new Implementation(MyJavaLintDetector.class, EnumSet.of(Scope.RESOURCE_FILE, Scope.JAVA_FILE)))
                    .addMoreInfo("file://explanation.doc")
                    .addMoreInfo("http://my.personal.blogger.com/aboutme.htm")
                    .addMoreInfo("mailto:lint@example.com");
    public void testGetBody(PsiMethod method) {
        method.getBody(); // ERROR - must use UAST
    }
    public void testGetBody(UMethod method) {
        method.getBody(); // ERROR - must use UAST
    }
    public void testGetContainingClass(UMethod method, UField field) {
        method.getContainingClass(); // ERROR - must use UAST
        field.getContainingClass(); // ERROR - must use UAST
    }
    public void testGetContainingClass(PsiMethod method, PsiField field) {
        method.getContainingClass(); // OK - legitimate uses after resolve
        field.getContainingClass(); // OK - legitimate uses after resolve
    }
    public void testEquals(PsiCallExpression element1, PsiExpression element2) {
        if (element1.equals(element2)) { }
        if (element2.equals(element1)) { }
        if (element1 == element2) { }
        if (element1 != element2) { }
    }
    public void testGetInitializer(PsiField field) {
        field.getInitializer(); // ERROR - must use UAST
    }
    public void testParents(PsiField field, UMethod method) {
        PsiElement parent = field.getParent(); // OK
        PsiElement parent = method.getParent(); // ERROR
        PsiTreeUtil.getParentOfType(field, PsiClass.class); // OK
        PsiTreeUtil.getParentOfType(method, PsiClass.class); // ERROR
    }

    public void testReport(JavaContext context, UCallExpression node) {
        context.report(ISSUE, node, context.getLocation(node),
            "Wrong use of LinearLayout.");
        context.report(ISSUE, node, context.getLocation(node),
            "First problem. Second problem.");
        context.report(ISSUE, node, context.getLocation(node),
            "This is teh typo");
        String message = "Welcome to Andriod";
        context.report(ISSUE, node, context.getLocation(node), message);
        context.report(ISSUE, node, context.getLocation(node),
            "Should you use `x ?: y` instead of ```foo ? 1 : 0``` ?");
    }
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

`src/test/pkg/MyKotlinLintDetector.kt`:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin linenumbers
/* Copyright (C) 2020 The Android Open Source Project */
package test.pkg
import com.intellij.psi.PsiCallExpression
import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiField
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import org.jetbrains.uast.UCallExpression

class MyKotlinLintDetector : Detector() {
    fun testGetBody(method: PsiMethod) {
        val body = method.body // ERROR - must use UAST
    }
    @Suppress("ReplaceCallWithBinaryOperator","ControlFlowWithEmptyBody")
    fun testEquals(element1: PsiCallExpression, element2: PsiExpression) {
        if (element1.equals(element2)) { }
        if (element2.equals(element1)) { }
        if (element1 == element2) { }
        if (element1 === element2) { }
        if (element1 != element2) { }
        if (element1 !== element2) { }
        if (element1 == null) { } // OK
        if (element1 === null) { } // OK
        if (element1 != null) { } // OK
        if (element1 !== null) { } // OK
    }
    @Suppress("UsePropertyAccessSyntax")
    fun testGetInitializer(field: PsiField) {
        field.getInitializer() // ERROR - must use UAST
        field.initializer // ERROR - must use UAST
    }
    fun testParents(field: PsiField) {
        val parent = field.parent
        val method = PsiTreeUtil.getParentOfType(field, PsiMethod::class.java)
    }

    fun testReport(context: JavaContext, node: UCallExpression) {
        context.report(ISSUE, node, context.getLocation(node),
             """
                    |Instead you should call foo().bar().baz() here.
                    |""".trimIndent())
    }

    companion object {
        private val IMPLEMENTATION =
            Implementation(
                MyKotlinLintDetector::class.java,
                Scope.JAVA_FILE_SCOPE
            )

        val ISSUE =
            Issue.create(
                id = "badlyCapitalized id",
                briefDescription = "checks MyLintDetector.",
                explanation = """
                    Some description here.
                    Here's a call: foo.bar.baz(args).
                    This line continuation is okay. \
                    But this one is missing a space.\
                    Okay?
                    """.trimIndent(),
                category = Category.INTEROPERABILITY_KOTLIN,
                moreInfo = "https://code.google.com/p/android/issues/detail?id=65351", // OBSOLETE
                priority = 4,
                severity = Severity.WARNING,
                implementation = IMPLEMENTATION
            )
            .addMoreInfo("https://issuetracker.google.com/issues/3733548") // ERROR - missing digit
            .addMoreInfo("https://issuetracker.google.com/issues/373354878") // OK - including digit
            .addMoreInfo("http://issuetracker.google.com/issues/37335487") // ERROR - http instead of https
            .addMoreInfo("https://b.corp.google.com/issues/139153781") // ERROR - don't point to buganizer with internal link
            .addMoreInfo("https://goo.gle/policy-storage-help") // OK - regression test for goo.gle
    }

    override fun visitAnnotationUsage(
        context: JavaContext,
        element: org.jetbrains.uast.UElement.UElement,
        annotationInfo: com.android.tools.lint.detector.api.AnnotationInfo,
        usageInfo: com.android.tools.lint.detector.api.AnnotationUsageInfo
    ) {
        // Invalid recursion!
        super.visitAnnotationUsage(context, element, annotationInfo, usageInfo)
    }

    fun misc() {
        System.out.print("Debugging")
        println("Debugging code")
    }
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

`src/test/pkg/MyIssueRegistry.kt`:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin linenumbers
package test.pkg
import com.android.tools.lint.client.api.IssueRegistry
class MyIssueRegistry : IssueRegistry() {
    override val issues = listOf(
        MyJavaLintDetector.ISSUE,
        MyKotlinLintDetector.Companion.ISSUE
    )
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

`src/test/pkg/MyVendorIssueRegistry.kt`:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin linenumbers
package test.pkg
import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.client.api.Vendor
class MyVendorIssueRegistry : IssueRegistry() {
    // Supplies a vendor: no warning
    override var vendor: Vendor? = Vendor("Unit test")
    override val issues = listOf(
        MyJavaLintDetector.ISSUE,
        MyKotlinLintDetector.Companion.ISSUE
    )
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

`src/test/pkg/InheritingRegistry.kt`:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin linenumbers
package test.pkg
import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.client.api.Vendor
class InheritingRegistry : MyVendorIssueRegistry() { // NO WARNING
    // This registry doesn't supply a vendor but inherits one; no warning
    override val issues = listOf(
        MyJavaLintDetector.ISSUE,
        MyKotlinLintDetector.Companion.ISSUE
    )
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

`src/test/pkg/MyKotlinLintDetectorTest.kt`:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin linenumbers
// Copyright (C) 2021 The Android Open Source Project
package test.pkg
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.detector.api.Detector
class MyKotlinLintDetectorTest : LintDetectorTest() {
    override fun getDetector(): Detector {
        return MyKotlinLintDetector()
    }

    fun testBasic() {
        val expected = """
            src/test/pkg/AlarmTest.java:9: Warning: Value will be forced up to 5000 as of Android 5.1; don't rely on this to be exact [ShortAlarm]
                    alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 50, 10, null); // ERROR
                                                                             ~~
            0 errors, 1 warnings
            """

        lint().files(
            kotlin(
                """
                fun test() {
                    println("Value=${"$"}")
                }
                """
            ),
            java(
                "src/test/pkg/AlarmTest.java",
                """
                    package test.pkg;

                    import android.app.AlarmManager;
                    @SuppressWarnings("ClassNameDiffersFromFileName")
                    public class AlarmTest {
                        public void test(AlarmManager alarmManager) {
                            alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, 60000, null); // OK
                            alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 6000, 70000, null); // OK
                            alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 50, 10, null); // ERROR
                            alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000,  // ERROR
                                    OtherClass.MY_INTERVAL, null);                          // ERROR
                        }

                        private static class OtherClass {
                            public static final long MY_INTERVAL = 1000L;
                        }
                    }
                    """.trimIndent()
            )
        ).run().expect(expected)
    }
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can also visit the
[source code](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LintDetectorDetectorTest.kt)
for the unit tests for this check to see additional scenarios.

The above example was automatically extracted from the first unit test
found for this lint check, `LintDetectorDetector.testProblems`.
To report a problem with this extracted sample, visit
https://issuetracker.google.com/issues/new?component=192708.

(##) Suppressing

You can suppress false positives using one of the following mechanisms:

* Using a suppression annotation like this on the enclosing
  element:

  ```kt
  // Kotlin
  @Suppress("LintImplDollarEscapes")
  fun method() {
     expect(...)
  }
  ```

  or

  ```java
  // Java
  @SuppressWarnings("LintImplDollarEscapes")
  void method() {
     expect(...);
  }
  ```

* Using a suppression comment like this on the line above:

  ```kt
  //noinspection LintImplDollarEscapes
  problematicStatement()
  ```

* Using a special `lint.xml` file in the source tree which turns off
  the check in that folder and any sub folder. A simple file might look
  like this:
  ```xml
  &lt;?xml version="1.0" encoding="UTF-8"?&gt;
  &lt;lint&gt;
      &lt;issue id="LintImplDollarEscapes" severity="ignore" /&gt;
  &lt;/lint&gt;
  ```
  Instead of `ignore` you can also change the severity here, for
  example from `error` to `warning`. You can find additional
  documentation on how to filter issues by path, regular expression and
  so on
  [here](https://googlesamples.github.io/android-custom-lint-rules/usage/lintxml.md.html).

* In Gradle projects, using the DSL syntax to configure lint. For
  example, you can use something like
  ```gradle
  lintOptions {
      disable 'LintImplDollarEscapes'
  }
  ```
  In Android projects this should be nested inside an `android { }`
  block.

* For manual invocations of `lint`, using the `--ignore` flag:
  ```
  $ lint --ignore LintImplDollarEscapes ...`
  ```

* Last, but not least, using baselines, as discussed
  [here](https://googlesamples.github.io/android-custom-lint-rules/usage/baselines.md.html).

<!-- Markdeep: --><style class="fallback">body{visibility:hidden;white-space:pre;font-family:monospace}</style><script src="markdeep.min.js" charset="utf-8"></script><script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js" charset="utf-8"></script><script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>